<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>网页版2048游戏</title>
		<style>
			#game{
				width: 500px;
				height: 600px;
				margin: 0 auto;
			}
			#game_container{
				background-color: cornflowerblue;
				position: relative;
				border-radius: 10px;
			}
			.game-cell{
				background-color: lightpink;
				position: absolute;
				border-radius: 10px;
			}
			.game-num{
				width: 0px;
				height: 0px;
				color: #fff;
				font-size: 40px;
				text-align: center;
				position: absolute;
				border-radius: 10px;
			}
			.game-num-2{
				background: #eee4da;
				color: #776e65;
			}
			.game-num-4{
				background: #ede0c8;
				color: #776e65;
			}
			.game-num-8{
				background: #f2b179;
			}
			.game-num-16{
				background: #f59563;
			}
			.game-num-32{
				background: #f67c5f;
			}
			.game-num-64{
				background: #f65e3b;
			}
			.game-num-128{
				background: #edcf72;
				font-size: 35px;
			}
			.game-num-256{
				background: #edcc61;
				font-size: 35px;
			}
			.game-num-512{
				background: #9c0;
				font-size: 35px;
			}
			.game-num-1024{
				background: #33b5e5;
				font-size: 30px;
			}
			.game-num-2048{
				background: #09c;
				font-size: 30px;
			}
			#fs{
				width: 500px;
				height: 100px;
				text-align: center;
				font-size: 25px;
				line-height: 100px;
			}
			.game-hide{
				display: none;
			}
			#game_over{
				z-index: 1;
				width: 500px;
				height: 500px;
				position: absolute;
				
				background-color: rgba(0, 0, 255, 0.2);
			}
			#game_over_info{
				margin-top: 160px;
				text-align: center;
				font-size: 30px;
				color: #fff;
			}
			#game_over span{
				display: block;
				width: 100px;
				height: 30px;
				background-color: chartreuse;
				font-size: 15px;
				text-align: center;
				line-height: 30px;
				margin: 0 auto;
				cursor: pointer;
				border-radius: 10px;
			}
			#game_restart:hover{
				background-color: maroon;
				color: #fff;
			}
		</style>
	</head>
	<body>
		<div id="game">
			<div id="fs">
				分数：<span id="game_score">0</span>
			</div>
			<div id="game_container">
				<div id="game_over" class="game-hide">
					<div id="game_over_info"></div>
					<span id="game_restart">重新开始</span>
				</div>
			</div>
		</div>
	</body>
</html>
<script src="./jquery-1.12.4.min.js"></script>
<script>
	//View
	function View(prefix,len,size,margin){
		this.prefix=prefix;
		this.len=len;
		this.size=size;
		this.margin=margin;
		this.container=$('#'+prefix+'_container');
		var containerSize=len*size+margin*(len+1);
		this.container.css({width:containerSize,height:containerSize});
		this.nums={};
		this.score=$('#'+prefix+'_score');
	}
	View.prototype={
		getPos:function(n){
			return this.margin+n*(this.size+this.margin);
		},
		init:function(){
			for(var x=0,len=this.len;x<len;x++){
				for(var y=0;y<len;y++){
					var $cell=$('<div class="'+this.prefix+'-cell"></div>');
					$cell.css({width:this.size+'px',height:this.size+'px',top:this.getPos(x),left:this.getPos(y)}).appendTo(this.container);
				}
			}
		},
		addNum:function(x,y,num){
			var $num=$('<div class="'+this.prefix+'-num '+this.prefix+'-num-'+num+'"></div>');
			$num.text(num).css({top:this.getPos(x)+parseInt(this.size/2),left:this.getPos(y)+parseInt(this.size/2)}).appendTo(this.container).animate({width:this.size+'px',height:this.size+'px',lineHeight:this.size+'px',top:this.getPos(x),left:this.getPos(y)},100);
			this.nums[x+'-'+y]=$num;
		},
		move:function(from,to){
			var fromIndex=from.x+'-'+from.y,toIndex=to.x+'-'+to.y;
			var clean=this.nums[toIndex];
			this.nums[toIndex]=this.nums[fromIndex];
			delete this.nums[fromIndex];
			var prefix=this.prefix+'-num-';
			var pos={top:this.getPos(to.x),left:this.getPos(to.y)};
			this.nums[toIndex].finish().animate(pos,200,function(){
				if(to.num>from.num){
					clean.remove();
					$(this).text(to.num).removeClass(prefix+from.num).addClass(prefix+to.num);
				}
			});
		},
		updateScore:function(score){
			this.score.text(score);
		},
		win:function(){
			$('#'+this.prefix+'_over_info').html('<p>您获胜了</p>');
			$('#'+this.prefix+'_over').removeClass(this.prefix+'-hide');
		},
		over:function(score){
			$('#'+this.prefix+'_over_info').html('<p>本次得分</p><p>'+score+'</p>');
			$('#'+this.prefix+'_over').removeClass(this.prefix+'-hide');
		},
		cleanNum:function(){
			this.nums={};
			$('#'+this.prefix+'_over').addClass(this.prefix+'-hide');
			$('.'+this.prefix+'-num').remove();
		},
	};
	//Game2048
	function Game2048(opt){
		var prefix=opt.prefix,len=opt.len,size=opt.size,margin=opt.margin;
		var view=new View(prefix,len,size,margin);
		view.init();
		var board=new Board(len);
		board.onGenerate=function(e){
			view.addNum(e.x,e.y,e.num);
		};
		var score=0;
		var winNum=2048;
		var isGameOver=true;
		board.onMove=function(e){
			view.move(e.from,e.to);
			if(e.to.num>e.from.num){
				score+=e.to.num;
				view.updateScore(score);
			}
			if(e.to.num>=winNum){
				isGameOver=true;
				setTimeout(function(){
					view.win();
				},300);
			}
		};
		board.onMoveComplete=function(e){
			if(e.moved){
				setTimeout(function(){
					board.generate();
				},200);
			}
			if(!board.canMove()){
				isGameOver=true;
				setTimeout(function(){
					view.over(score);
				},300);
			}
		};
		$(document).keydown(function(e){
			if(isGameOver){
				return false;
			}
			switch(e.which){
				case 37:board.moveLeft();break;
				case 38:board.moveUp();break;
				case 39:board.moveRight();break;
				case 40:board.moveDown();break;
			}
		});
		function start(){
			score=0;
			view.updateScore(0);
			view.cleanNum();
			board.init();
			board.generate();
			board.generate();
			isGameOver=false;
		}
		$('#'+prefix+'_restart').click(start);
		start();
	}
	//Board
	function Board(len){
		this.len=len;
		this.arr=[];
	}
	Board.prototype={
		init:function(){
			for(var arr=[],len=this.len,x=0;x<len;x++){
				arr[x]=[];
				for(var y=0;y<len;y++){
					arr[x][y]=0;
				}
			}
			this.arr=arr;
		},
		generate:function(){
			var empty=[];
			for(var x=0,arr=this.arr,len=arr.length;x<len;x++){
				for(var y=0;y<len;y++){
					if(arr[x][y]===0){
						empty.push({x:x,y:y});
					}
				}
			}
			if(empty.length<1){
				return false;
			}
			var pos=empty[Math.floor((Math.random()*empty.length))];
			this.arr[pos.x][pos.y]=Math.random()<0.5?2:4;
			this.onGenerate({x:pos.x,y:pos.y,num:this.arr[pos.x][pos.y]});
		},
		onGenerate:function(){},
		moveLeft:function(){
			var moved=false;
			for(var x=0,len=this.arr.length;x<len;x++){
				for(var y=0,arr=this.arr[x];y<len;y++){
					for(var next=y+1;next<len;next++){
						if(arr[next]===0){
							continue;
						}
						if(arr[y]===0){
							arr[y]=arr[next];
							this.onMove({from:{x:x,y:next,num:arr[next]},to:{x:x,y:y,num:arr[y]}});
							arr[next]=0;
							moved=true;
							y--;
						}else if(arr[y]===arr[next]){
							arr[y]*=2;
							this.onMove({from:{x:x,y:next,num:arr[next]},to:{x:x,y:y,num:arr[y]}});
							arr[next]=0;
							moved=true;
						}
						break;
					}
				}
			}
			this.onMoveComplete({moved:moved});
		},
		moveRight:function(){
			var moved=false;
			for(var x=0,len=this.arr.length;x<len;x++){
				for(var y=len-1,arr=this.arr[x];y>=0;y--){
					for(var next=y-1;next>=0;next--){
						if(arr[next]===0){
							continue;
						}
						if(arr[y]===0){
							arr[y]=arr[next];
							this.onMove({from:{x:x,y:next,num:arr[next]},to:{x:x,y:y,num:arr[y]}});
							arr[next]=0;
							moved=true;
							y++;
						}else if(arr[y]===arr[next]){
							arr[y]*=2;
							this.onMove({from:{x:x,y:next,num:arr[next]},to:{x:x,y:y,num:arr[y]}});
							arr[next]=0;
							moved=true;
						}
						break;
					}
				}
			}
			this.onMoveComplete({moved:moved});
		},
		moveUp:function(){
			var moved=false;
			for(var y=0,len=this.arr.length;y<len;y++){
				for(var x=0,arr=this.arr;x<len;x++){
					for(var next=x+1;next<len;next++){
						if(arr[next][y]===0){
							continue;
						}
						if(arr[x][y]===0){
							arr[x][y]=arr[next][y];
							this.onMove({from:{x:next,y:y,num:arr[next][y]},to:{x:x,y:y,num:arr[x][y]}});
							arr[next][y]=0;
							moved=true;
							x--;
						}else if(arr[x][y]===arr[next][y]){
							arr[x][y]*=2;
							this.onMove({from:{x:next,y:y,num:arr[next][y]},to:{x:x,y:y,num:arr[x][y]}});
							arr[next][y]=0;
							moved=true;
						}
						break;
					}
				}
			}
			this.onMoveComplete({moved:moved});
		},
		moveDown:function(){
			var moved=false;
			for(var y=0,len=this.arr.length;y<len;y++){
				for(var x=len-1,arr=this.arr;x>=0;x--){
					for(var next=x-1;next>=0;next--){
						if(arr[next][y]===0){
							continue;
						}
						if(arr[x][y]===0){
							arr[x][y]=arr[next][y];
							this.onMove({from:{x:next,y:y,num:arr[next][y]},to:{x:x,y:y,num:arr[x][y]}});
							arr[next][y]=0;
							moved=true;
							x++;
						}else if(arr[x][y]===arr[next][y]){
							arr[x][y]*=2;
							this.onMove({from:{x:next,y:y,num:arr[next][y]},to:{x:x,y:y,num:arr[x][y]}});
							arr[next][y]=0;
							moved=true;
						}
						break;
					}
				}
			}
			this.onMoveComplete({moved:moved});
		},
		onMove:function(){},
		onMoveComplete:function(){},
		canMove:function(){
			for(var x=0,arr=this.arr,len=arr.length;x<len;x++){
				for(var y=0;y<len;y++){
					if(arr[x][y]===0){
						return true;
					}
					var curr=arr[x][y],right=arr[x][y+1];
					var down=arr[x+1]?arr[x+1][y]:null;
					if(right===curr||down===curr){
						return true;
					}
				}
			}
			return false;
		},
	};
	
	Game2048({prefix:'game',len:4,size:100,margin:20});
</script>